package arcade;

import graphics.Animatable;

import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

/**
 * Actor
 * 
 * An actor is an animatable role player. Actors have actions and reactions
 * assigned to them. Actions are stepped through on each animation whereas
 * reactions are stepped through only when a second actor of a given type is
 * intersected with. Behaviors can be assigend to either role either by
 * specifying their role's name or by passing a reference to the behavior
 * itself.
 * 
 * @author Alexander Schearer <aas11@duke.edu>
 */
abstract public class Actor extends RolePlayer implements Animatable {

	private List<Behavior> myActions;

	private HashMap<String, List<Behavior>> myReactions;

	private Queue<Behavior> myTrashHeap;

	public Actor(String role) {
		super(role);
		myActions = new ArrayList<Behavior>();
		myReactions = new HashMap<String, List<Behavior>>();
		myTrashHeap = new LinkedList<Behavior>();
	}

	/**
	 * Add a new action to this actor
	 * 
	 * Actions are stepped through on each step. Actions must be unique in that
	 * there can only be one action for any given role present.
	 * 
	 * @param b
	 * @return
	 */
	public boolean assignAction(Behavior b) {
		if (myActions.contains(b))
			return false;
		return myActions.add(b);
	}

	/**
	 * Remove an action from this actor
	 * 
	 * Attempt to remove the specified behavior from the actor. This will return
	 * false if nothing was removed or an error occurred and true if a behavior
	 * was removed.
	 * 
	 * @param b
	 * @return
	 */
	public boolean removeAction(String b) {
		for (int i = 0; i < myActions.size(); i++) {
			if (myActions.get(i).equals(b)) {
				myActions.remove(i);
				return true;
			}
		}
		return false;
	}

	public boolean removeAction(Behavior b) {
		return myActions.remove(b);
	}

	/**
	 * Assign Behavior
	 * 
	 * Attempt to assign a new behavior to the given role. Whenever this actor
	 * intersects with a second which is playing the given role then every
	 * behavior associated with the role will be stepped through once. The
	 * behaviors cannot be repeated, so for each behavior of a given role there
	 * can be only one present as a reaction to a given actor.
	 * 
	 * @param role
	 * @param b
	 * @return
	 */
	public boolean assignReaction(String role, Behavior b) {
		List<Behavior> existing_behaviors = getBehaviorsFor(role);
		if (existing_behaviors.contains(b))
			return false;
		existing_behaviors.add(b);
		myReactions.put(role, existing_behaviors);
		return true;
	}

	/**
	 * Remove a specified behavior
	 * 
	 * Return true if somethign was removed from the list or false if nothing
	 * was removed or an error occurred.
	 * 
	 * @param role
	 * @param b
	 * @return
	 */
	public boolean removeReaction(String role, String b) {
		List<Behavior> existing_behaviors = getBehaviorsFor(role);
		for (int i = 0; i < existing_behaviors.size(); i++) {
			if (existing_behaviors.get(i).equals(b)) {
				existing_behaviors.remove(i);
				myReactions.put(role, existing_behaviors);
				return true;
			}
		}
		return false;
	}

	public boolean removeReaction(String role, Behavior b) {
		List<Behavior> existing_behaviors = getBehaviorsFor(role);
		existing_behaviors.remove(b);
		myReactions.put(role, existing_behaviors);
		return true;
	}

	/**
	 * Retrieve the list of behaviors associated with the passed role
	 * 
	 * This will retrieve all the behaviors which are meant to be fired when
	 * this actor intersects with another of the specified type.
	 * 
	 * @param role
	 * @return
	 */
	private List<Behavior> getBehaviorsFor(String role) {
		List<Behavior> list = myReactions.get(role);
		if (list == null)
			list = new ArrayList<Behavior>();
		return list;
	}

	public void animate() {
		for (Behavior b : myActions) {
			b.step();
			if (b.isTrashed())
				myTrashHeap.add(b);
		}
		while (myTrashHeap.size() > 0) {
			removeAction(myTrashHeap.remove());
		}
	}

	public void collide(Actor obstacle) {
		List<Behavior> reactions = myReactions.get(obstacle.getRole());
		if (reactions == null)
			reactions = new ArrayList<Behavior>();
		for (Behavior b : reactions) {
			b.step();
			if (b.isTrashed())
				myTrashHeap.add(b);
		}
		while (myTrashHeap.size() > 0) {
			removeReaction(obstacle.getRole(), myTrashHeap.remove());
		}
	}

	abstract public void paint(Graphics2D pen);
}
